package cn.axj.crypt.handler;

import com.alibaba.fastjson.JSON;
import cn.axj.crypt.annotation.Encryption;
import cn.axj.crypt.config.CryptProperties;
import cn.axj.crypt.exception.BindingException;
import cn.axj.crypt.secret.BaseAlgorithmHttpInputMessage;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;


/**
 * @author aoxiaojun
 * @date 2020/12/15 16:40
 **/
@ControllerAdvice
public class ResponseEncryptionAdvice implements ResponseBodyAdvice {

    @Resource
    private CryptProperties cryptProperties;

    private List<BaseAlgorithmHttpInputMessage> list = new ArrayList<>();

    public ResponseEncryptionAdvice(List<BaseAlgorithmHttpInputMessage> list) throws BindingException {
        this.addAll(list);
    }

    private void addAll(List<BaseAlgorithmHttpInputMessage> list) throws BindingException {
        String temp;
        for(int i=0;i<list.size();i++){
            temp = list.get(i).getBinding();
            for (int j = i+1;j<list.size();j++){
                String binding = list.get(j).getBinding();
                if(temp.equals(binding)){
                    throw new BindingException("The algorithm implements binding value is repeat!please check it exists two value of " + binding);
                }
            }
        }
        this.list.addAll(list);
    }

    @Override
    public boolean supports(MethodParameter methodParameter, Class aClass) {
        if(!cryptProperties.isResponseEncryption()){
            return false;
        }
        Encryption encryption;
        encryption = methodParameter.getMethodAnnotation(Encryption.class);
        if(encryption == null){
            encryption = methodParameter.getContainingClass().getAnnotation(Encryption.class);
        }
        return encryption != null;
    }

    @Override
    public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
        if(o == null){
            return null;
        }
        Encryption encryption;
        encryption = methodParameter.getMethodAnnotation(Encryption.class);
        Class<?> targetClass = methodParameter.getContainingClass();
        if(encryption == null){
            encryption = targetClass.getAnnotation(Encryption.class);
        }

        try {
            String bind = encryption.bind();
            String fieldName = encryption.fieldName();
            for (BaseAlgorithmHttpInputMessage baseAlgorithmHttpInputMessage : list) {
                if(bind.equals(baseAlgorithmHttpInputMessage.getBinding())){
                    if(StringUtils.isNotBlank(fieldName)) {
                        Field dataFiled = o.getClass().getDeclaredField(fieldName);
                        dataFiled.setAccessible(true);
                        Object dataObject = dataFiled.get(o);
                        String encrypt = baseAlgorithmHttpInputMessage.encrypt(JSON.toJSONString(dataObject));
                        dataFiled.set(o, encrypt);
                        return o;
                    }else{
                        return baseAlgorithmHttpInputMessage.encrypt(JSON.toJSONString(o));
                    }
                }
            }
            //找不到绑定的加密对象
            String methodName = Objects.requireNonNull(methodParameter.getMethod()).getName();
            String targetClassName = targetClass.getSimpleName();

            throw new BindingException("Unable to find a binding object of encryption,targetClass is "+targetClassName+",method is "+methodName);
        } catch (Exception e){
            e.printStackTrace();
        }
        return o;
    }
}
